package com.nononsenseapps.feeder.parser

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import okhttp3.Cache
import okhttp3.OkHttpClient
import okhttp3.Request
import okio.BufferedSource
import okio.buffer
import okio.source
import java.io.File
import java.io.IOException
import java.util.concurrent.TimeUnit

// 10 mins
const val MIN_MAXAGE = 600
val MAX_AGE_PATTERN = """max-age=(\d+)""".toRegex()
const val USER_AGENT_STRING = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:0.9.4)"

const val DEFAULT_CONNECT_TIMEOUT_SECS:Long=30L

const val NOSET_CONNECT_TIMEOUT_SECS:Long=0

fun cachingHttpClient(cacheDirectory: File? = null,
                      cacheSize: Long = 10L * 1024L * 1024L,
                      trustAllCerts: Boolean = true,
                      connectTimeoutSecs: Long = DEFAULT_CONNECT_TIMEOUT_SECS,
                      readTimeoutSecs: Long = 30L): OkHttpClient {
    val builder: OkHttpClient.Builder = OkHttpClient.Builder()

    if (cacheDirectory != null) {
        builder.cache(Cache(cacheDirectory, cacheSize))
    }

    // Not all web servers have good cache settings defined, so overwrite feeds to allow caching
    // for 20 mins if whatever it says is bad
    builder.addNetworkInterceptor {
        val response = it.proceed(it.request())

        try {
            val cacheHeaders = response.headers("Cache-Control")

            var maxAge = -1

            for (header in cacheHeaders) {
                MAX_AGE_PATTERN.find(header)?.let {
                    maxAge = it.groupValues.last().toInt()
                }
            }

            maxAge = maxOf(maxAge, MIN_MAXAGE)

            response.newBuilder()
                    .header("Cache-Control", "public, max-age=$maxAge")
                    .header("User-Agent", USER_AGENT_STRING)
                    .build()

        } catch (ignored: Throwable) {
            response
        }
    }

    builder.connectTimeout(connectTimeoutSecs, TimeUnit.SECONDS)
            .readTimeout(readTimeoutSecs, TimeUnit.SECONDS)

    if (trustAllCerts) {
        builder.trustAllCerts()
    }

    return builder.build()
}

fun feedAdapter(): JsonAdapter<Feed> = Moshi.Builder().build().adapter(Feed::class.java)

abstract class AbstractJsonParser<T>(private val httpClient: OkHttpClient,
                                     private val jsonAdapter: JsonAdapter<T>) {
    @JvmOverloads
     constructor(cacheDirectory: File? = null,
                          cacheSize: Long = 10L * 1024L * 1024L,
                          trustAllCerts: Boolean = true,
                          connectTimeoutSecs: Long = 5L,
                          readTimeoutSecs: Long = 5L,
                          jsonAdapter: JsonAdapter<T>) : this(
            cachingHttpClient(
                    cacheDirectory = cacheDirectory,
                    cacheSize = cacheSize,
                    trustAllCerts = trustAllCerts,
                    connectTimeoutSecs = connectTimeoutSecs,
                    readTimeoutSecs = readTimeoutSecs
            ),
            jsonAdapter
    )

    /**
     * Download a JSONFeed and parse it
     */
    fun parseUrl(url: String): T {
        val request: Request
        try {
            request = Request.Builder()
                    .url(url)
                    .build()
        } catch (error: Throwable) {
            throw IllegalArgumentException("Bad URL:$url. Perhaps it is missing an http:// prefix?", error)
        }

        val response = httpClient.newCall(request).execute()

        if (!response.isSuccessful) {
            throw IOException("Failed to download feed: $url")
        }

        val body = response.body
        if (body != null) {
            body.source().inputStream().use {
                return parseJsonStream(it.source().buffer())
            }
        } else {
            throw IOException("Failed to parse feed: body was NULL:$url")
        }
    }

    /**
     * Parse a JSONFeed
     */
     fun parseJson(json: String): T =
            json.byteInputStream().use { return parseJsonStream(it.source().buffer()) }

    /**
     * Parse a JSONFeed
     */
     fun parseJsonBytes(json: ByteArray): T =
            json.inputStream().use { return parseJsonStream(it.source().buffer()) }


    /**
     * Parse a JSONFeed
     */
    @Suppress("MemberVisibilityCanPrivate")
     fun parseJsonStream(json: BufferedSource): T {
        val result = jsonAdapter.fromJson(json)

        when {
            result != null -> return result
            else -> throw IOException("Failed to parse JSONFeed")
        }
    }

    fun getHttpClient():OkHttpClient{
        return httpClient
    }
}

/**
 * A parser for JSONFeeds. CacheDirectory and CacheSize are only relevant if feeds are downloaded. They are not used
 * for parsing JSON directly.
 */
class JsonFeedParser(httpClient: OkHttpClient) : AbstractJsonParser<Feed>(httpClient, feedAdapter()) {

    constructor(cacheDirectory: File? = null,
                cacheSize: Long = 10L * 1024L * 1024L,
                trustAllCerts: Boolean = true,
                connectTimeoutSecs: Long = 5L,
                readTimeoutSecs: Long = 5L) : this(
                       cachingHttpClient(
                               cacheDirectory = cacheDirectory,
                               cacheSize = cacheSize,
                               trustAllCerts = trustAllCerts,
                               connectTimeoutSecs = connectTimeoutSecs,
                               readTimeoutSecs = readTimeoutSecs
                       )
                )
}

@JsonClass(generateAdapter = true)
data class Feed(val version: String? = "https://jsonfeed.org/version/1",
                val title: String?,
                val home_page_url: String? = null,
                val feed_url: String? = null,
                val description: String? = null,
                val user_comment: String? = null,
                val next_url: String? = null,
                val icon: String? = null,
                val favicon: String? = null,
                val author: Author? = null,
                val expired: Boolean? = null,
                val hubs: List<Hub>? = null,
                val items: List<Item>?,
                var responseHash :Int = 0
                )

@JsonClass(generateAdapter = true)
data class Author(val name: String? = null,
                  val url: String? = null,
                  val avatar: String? = null)

@JsonClass(generateAdapter = true)
data class Item(var id: String?,
                val url: String? = null,
                val external_url: String? = null,
                val title: String? = null,
                var content_html: String? = null,
                var content_text: String? = null,
                var summary: String? = null,
                var image: String? = null,
                val banner_image: String? = null,
                val date_published: String? = null,
                val date_modified: String? = null,
                val author: Author? = null,
                val tags: List<String>? = null,
                val attachments: List<Attachment>? = null,
                var imageCount:Int?=0,
                var unfold:Boolean=false)

@JsonClass(generateAdapter = true)
data class Attachment(val url: String?,
                      val mime_type: String? = null,
                      val title: String? = null,
                      val size_in_bytes: Long? = null,
                      val duration_in_seconds: Long? = null)

@JsonClass(generateAdapter = true)
data class Hub(val type: String?,
               val url: String?)
